package com.hzz.hzzgateway;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hzz.hzzgateway.base.ResponseResult;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;

import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Set;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

@Component
public class WrapResultGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
    private final static long JS_MAX_NUMBER=9007199254740992L;


    public WrapResultGatewayFilterFactory() {

    }

    @Override
    public GatewayFilter apply(NameValueConfig config) {
        return new WrapResultGatewayFilter(config);
    }

    private void replaceLongToStr(JSONObject jsonObject){
        Set<String> keys = jsonObject.keySet();
        for (String key : keys) {
            Object value=jsonObject.get(key);
            if(value!=null){
                if(ObjectUtil.isBasicType(value)&&value instanceof Long&&((Long) value)>JS_MAX_NUMBER){
                    jsonObject.set(key,String.valueOf(value));
                }else if(value instanceof JSONObject){
                    replaceLongToStr((JSONObject)value);
                }else if(value instanceof JSONArray){
                    replaceArrayLongToStr((JSONArray)value);
                }
            }
        }
    }
    private void replaceArrayLongToStr(JSONArray jsonArray){
        for (int i = 0; i < jsonArray.size(); i++) {
            Object o = jsonArray.get(i);
            if(o instanceof JSONObject){
                replaceLongToStr((JSONObject)o);
            }else if(o instanceof JSONArray){
                replaceArrayLongToStr((JSONArray)o);
            }else{
                if(o!=null){
                    if(o instanceof Long&&((Long) o)>JS_MAX_NUMBER){
                        jsonArray.set(i,String.valueOf(o));
                    }
                }
            }
        }

    }
    public class WrapResultGatewayFilter implements GatewayFilter, Ordered {
        private boolean longToString;

        public WrapResultGatewayFilter(NameValueConfig config) {
            if("longToString".equals(config.getName())){
                this.longToString= BooleanUtil.toBoolean(config.getValue());
            }
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return chain.filter(exchange.mutate().response(decorate(exchange)).build());


        }
        private boolean needTranTextToJson(String contentType){
            return contentType!=null&&contentType.contains(MediaType.TEXT_PLAIN_VALUE);
        }
        @SuppressWarnings("unchecked")
        ServerHttpResponse decorate(ServerWebExchange exchange) {
            return new ServerHttpResponseDecorator(exchange.getResponse()) {

                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

                    Class inClass = String.class;
                    Class outClass = ResponseResult.class;

                    String orct = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    if("OK".equals(exchange.getResponse().getHeaders().getFirst("no-api-response"))){
                        return super.writeWith(Flux.from(body));
                    }
                    if(exchange.getResponse().getStatusCode()!=null&& HttpStatus.NOT_FOUND.value()==exchange.getResponse().getStatusCode().value()){
                        return super.writeWith(Flux.from(body));
                    }
                    if(orct!=null&&!orct.contains(MediaType.APPLICATION_JSON_VALUE)&&!orct.contains(MediaType.TEXT_PLAIN_VALUE)){
                        return super.writeWith(Flux.from(body));
                    }
                    HttpHeaders httpHeaders = new HttpHeaders();

                    httpHeaders.add(HttpHeaders.CONTENT_TYPE, orct);

                    ClientResponse clientResponse = ClientResponse
                            .create(exchange.getResponse().getStatusCode())
                            .headers(headers -> headers.putAll(httpHeaders))
                            .body(Flux.from(body)).build();

                    Mono modifiedBody = clientResponse.bodyToMono(inClass)
                            .flatMap(originalBody -> {
                                /*System.out.println("====================");
                                System.out.println(originalResponseContentType+":"+originalBody.getClass().getSimpleName()+":"+originalBody);*/
                                String bodyStr = originalBody.toString();
                                if(MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(orct)){
                                    if(JSONUtil.isJsonObj(bodyStr)){
                                        JSONObject jsonObject = JSONUtil.parseObj(bodyStr);
                                        boolean errorCode = jsonObject.containsKey("errorCode");
                                        boolean message = jsonObject.containsKey("message");
                                        boolean success = jsonObject.containsKey("success");
                                        if(errorCode&&message&&success){
                                            return Mono.just(JSONUtil.parse(bodyStr));
                                        }else{
                                            JSONObject jo = JSONUtil.parseObj(bodyStr);
                                            if(longToString){
                                                WrapResultGatewayFilterFactory.this.replaceLongToStr(jo);
                                            }
                                            return Mono.just(ResponseResult.getOK(jo));
                                        }
                                    }else if(JSONUtil.isJsonArray(bodyStr)) {
                                        JSONArray jsonArray = JSONUtil.parseArray(bodyStr);
                                        if(longToString){
                                            WrapResultGatewayFilterFactory.this.replaceArrayLongToStr(jsonArray);
                                        }
                                        return Mono.just(ResponseResult.getOK(jsonArray));
                                    }else{
                                        if(NumberUtil.isDouble(bodyStr)){
                                            return Mono.just(ResponseResult.getOK(NumberUtil.parseDouble(bodyStr)));
                                        }else if(NumberUtil.isLong(bodyStr)){
                                            long l = NumberUtil.parseLong(bodyStr);
                                            return Mono.just(ResponseResult.getOK(longToString&&l>WrapResultGatewayFilterFactory.JS_MAX_NUMBER?bodyStr:l));
                                        }else if(bodyStr.equals("true")||bodyStr.equals("false")){
                                            return Mono.just(ResponseResult.getOK(BooleanUtil.toBoolean(bodyStr)));
                                        }
                                    }

                                }
                                return Mono.just(needTranTextToJson(orct)?ResponseResult.getOK(originalBody):originalBody);
                            }).switchIfEmpty(Mono.defer(() -> Mono.just(ResponseResult.getOK())));

                    BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass);
                    exchange.getResponse().setStatusCode(HttpStatus.OK);
                    HttpHeaders orHeaders = exchange.getResponse().getHeaders();
                    if(needTranTextToJson(orct)){
                        orHeaders.setContentType(MediaType.APPLICATION_JSON);
                    }

                    CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, orHeaders);
                    return bodyInserter.insert(outputMessage, new BodyInserterContext())
                            .then(Mono.defer(() -> {
                                Flux<DataBuffer> messageBody = outputMessage.getBody();
                                HttpHeaders headers = getDelegate().getHeaders();
                                if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
                                    messageBody = messageBody.doOnNext(data -> headers
                                            .setContentLength(data.readableByteCount()));
                                }
                                return getDelegate().writeWith(messageBody);
                            }));
                }

                @Override
                public Mono<Void> writeAndFlushWith(
                        Publisher<? extends Publisher<? extends DataBuffer>> body) {
                    return writeWith(Flux.from(body).flatMapSequential(p -> p));
                }
            };
        }

        @Override
        public int getOrder() {
            return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
        }


    }


}
